package com.yaohj.service.jwt;

import com.alibaba.fastjson.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Service
public class JwtServiceImpl {

    @Value("${application.security.jwt.secret-key}")
    private String secretKey;
    @Value("${application.security.jwt.expiration}")
    private long jwtExpiration;
    @Value("${application.security.jwt.refresh-token.expiration}")
    private long refreshExpiration;
    @Resource
    private RedisTemplate<String, String> redisTemplate;

    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

    public String generateToken(UserDetails userDetails) {
        return generateToken(new HashMap<>(), userDetails);
    }

    public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {

        return buildToken(extraClaims, userDetails, jwtExpiration);
    }


    public String generateRefreshToken(String token) {
        System.out.println("=== generateRefreshToken === ");
        try {
            DecodedJWT jwt = JWT.decode(token);
            String username = jwt.getSubject();
            Algorithm algorithm = Algorithm.HMAC256(secretKey);
            Date now = new Date();
            Date expiryDate = new Date(now.getTime() + refreshExpiration);
            String redisKey = generateUserRedisKey(username, token);
            redisTemplate.opsForValue().set(redisKey, username, refreshExpiration, TimeUnit.MILLISECONDS);
            return JWT.create().withSubject(username).withIssuedAt(now).withExpiresAt(expiryDate).sign(algorithm);
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    private String buildToken(Map<String, Object> extraClaims, UserDetails userDetails, long expiration) {
        System.out.println("=== 创建token === ");
        //结合自身业务场景，有些系统将用户的一些基本信息（id，username,roles）全部放在了以json的形式放在了token中
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expiration);

        Algorithm algorithm = Algorithm.HMAC256(secretKey);
        String token = JWT.create()
                .withSubject(JSON.toJSONString(userDetails))
                .withIssuedAt(now)
                .withExpiresAt(expiryDate)
                .sign(algorithm);
        //将token存入redis中，并设置过期时间
        String redisKey = generateUserRedisKey(userDetails.getUsername(), token);
        redisTemplate.opsForValue().set(redisKey, userDetails.getUsername(), expiration, TimeUnit.MILLISECONDS);

        return token;
    }


    public boolean isTokenValid(String token) {
        System.out.println("=== token有效性校验 === ");
        try {
            Algorithm algorithm = Algorithm.HMAC256(secretKey);
            JWT.require(algorithm).build().verify(token);
            System.out.println(" === 有效的token === ");
            return true;
        } catch (Exception e) {
            System.err.println(" === 无效的token === ");
            return false;
        }
    }

    private boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    private Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    private Claims extractAllClaims(String token) {
        return Jwts.parserBuilder().setSigningKey(getSignInKey()).build().parseClaimsJws(token).getBody();
    }

    public String getUsernameFromToken(String token) {
        System.out.println("=== getUsernameFromToken === ");
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getSubject();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    private Key getSignInKey() {
        byte[] keyBytes = Decoders.BASE64.decode(secretKey);
        return Keys.hmacShaKeyFor(keyBytes);
    }

    public String generateUserRedisKey(String username, String token) {
        return username + "::" + token;
    }
}
